/**
* AutoSWF - Cody Cushing. March 26, 2009
* Copyright (c) 2009 Cody Cushing
* No use restrictions. Maintain this notice in all versions. 
**/

package com.autoswf.core
{
	import flash.errors.IllegalOperationError;
	import flash.utils.Dictionary;
	
	/**
	 * Core component for bi-directional chain of command messaging. Creates an undirected graph of parents, 
	 * and directed graph of children. References passed up the chain are passed to all parents, references
	 * passed down the chain are given to the node with matching id in path address.  
	 */	
	public class ChainLink implements IChainLink
	{
		public static function join(parent:IChainLink, child:IChainLink):void
		{
			parent.addChildLink(child);
			child.addParentLink(parent);
		}
		
		public static function sever(parent:IChainLink, child:IChainLink):void
		{
			parent.removeChildLink(child);
			child.removeParentLink(parent);
		}
		
		public static const DELIMITER:String = Delimiters.PATH_DELIMITER;
		public var sendUpHook:Function;
		
		protected var parentList:Dictionary = new Dictionary(true);
		protected var childLinkHash:Dictionary = new Dictionary(true);
		protected var childLinkList:Dictionary = new Dictionary(true);
		
		private var _client:Object = this;
		
		private var childLinkIndex:int = 0;
		private var _id:String;
		
		
		/**
		 * Constructor
		 *  
		 * @param id  The id of this node. Must be different from sibling id for proper addressing by parent nodes.
		 */
		public function ChainLink(id:String)
		{
			this._id = id;
		}
		
		public function get id():String
		{
			return _id;
		}
		
		/**
		 * @param value  The target to test the payload of the object reference on. 
		 */
		public function set client(value:Object):void
		{
			_client = value;
		}

		public function addParentLink(parentChainLink:IChainLink):void
		{
			parentList[parentChainLink.id] = parentChainLink;
		}
		
		public function removeParentLink(parentChainLink:IChainLink):void
		{
			delete parentList[parentChainLink.id];
		}
		
		public function addChildLink(childChainLink:IChainLink):void
		{
			if(childLinkHash.hasOwnProperty(childChainLink.id))
			{
				throw new IllegalOperationError("Id collision, adding child with same id: " + childChainLink.id);
			}
			childLinkHash[childChainLink.id] = childChainLink;
			childLinkList[childLinkIndex++] = childChainLink.id; /* used for tree traversal */
		}
		
		public function removeChildLink(childChainLink:IChainLink):void
		{
			delete childLinkHash[childChainLink.id];
			/* to do - resolve index gaps and maintain iterative search */;
		}
		
		/**
		 * Method to pass an object reference to subscribing parent nodes. Override this 
		 * method to create a custom handler for incoming references. 
		 *  
		 * @param param  An object whose reference will be passed to all parent nodes. 
		 */
		public function sendUpChain(param:*):void
		{
			if(!(param is Transport))
			{
				param = new Transport(param);
			}
			if(sendUpHook != null)
			{
				sendUpHook(param);
			}
			notifyParents(param);
		}
		
		protected function notifyParents(transport:Transport):void
		{
			transport.pushPath(this);
			
			for(var p:String in parentList)
			{ 
				IChainLink(parentList[p]).sendUpChain(transport);
			}
		}
		
		/**
		 * Method to pass an object reference to a specific node via a path address. If the next node id not 
		 * a member of this nodes directed vertices, the reference is no longer passed, instead it is checked 
		 * against this link's client's public methods and properties. If a match is found, that method is 
		 * invoked or that property is returned.
		 * 
		 * To do: make return value an object wrapped in a transport. 
		 * 
		 * @param path  A series of node ids to pass this reference to.
		 * @return      The result of a function or property test at the final location.
		 */
		public function sendDownChain(path:*):String
		{
			if(!(path is Transport))
			{
				path = new Transport(path);
			}
			
			var transport:Transport = Transport(path);
			var proposition:String = transport.proposition;

			/* If it's a childChainLink pass it down, building the return path */
			if(childLinkHash.hasOwnProperty(proposition))
			{
				transport.popPath(this);
				var childChainLink:IChainLink = childLinkHash[proposition];
				return childChainLink.sendDownChain(transport);
			}
			
			/* if it's a public function then invoke it */
 			if(_client.hasOwnProperty(proposition) && _client[proposition] is Function)
			{
				return _client[proposition]();
			}
			
			/* otherwise it's a property, return its value */
 			if(_client.hasOwnProperty([proposition]))
			{
				return _client[proposition];
			}
			
			return null;
		}
		
		public function toString():String
		{
			return this._id;
		}
	}
}